package com.cheer.security;

import cn.hutool.core.collection.CollUtil;
import com.cheer.dao.AccountDao;
import com.cheer.entity.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.List;
import java.util.Optional;

/**
 * @author cheer
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    public static final String FAILED_LOGIN = "failedLogin";

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Autowired
    private RestAccessDeniedHandler restAccessDeniedHandler;

    @Autowired
    private RestAuthenticationFailureHandler restAuthenticationFailureHandler;

    @Autowired
    private RestAuthenticationSuccessHandler restAuthenticationSuccessHandler;

    @Autowired
    private RestLogoutSuccessHandler restLogoutSuccessHandler;


    /*
     * 解决Security访问Swagger2被拦截的问题；
     * 允许外部资源访问
     * 便于开发 上线时候务必注释掉
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        // allow Swagger URL to be accessed without authentication
        web.ignoring().antMatchers(
                "/swagger-ui.html",
                "/v2/api-docs", // swagger api json
                "/swagger-resources/configuration/ui", // 用来获取支持的动作
                "/swagger-resources", // 用来获取api-docs的URI
                "/swagger-resources/configuration/security", // 安全选项
                "/swagger-resources/**",
                "/login.html",
                //补充路径，近期在搭建swagger接口文档时，通过浏览器控制台发现该/webjars路径下的文件被拦截，故加上此过滤条件即可。(2020-10-23)
                "/webjars/**",
                // 德鲁伊数据库监控
                "/druid/**"

        );
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.headers().frameOptions().disable();

        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter("/auth/login", "POST");
        validateCodeFilter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);

        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()
                .loginProcessingUrl("/auth/login")
                .successHandler(restAuthenticationSuccessHandler)
                .failureHandler(restAuthenticationFailureHandler)
                .and()
                .authorizeRequests()
                .antMatchers("/auth/account").authenticated()
                .antMatchers("/auth/**").permitAll()
                .antMatchers("/document/upload").permitAll()
                .antMatchers("/organization/join").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf()
                .disable();

        http.sessionManagement().and().sessionManagement()
                .maximumSessions(10)
                .sessionRegistry(getSessionRegistry());

        http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint)
                .and().exceptionHandling().accessDeniedHandler(restAccessDeniedHandler);

        http.logout()
                .logoutUrl("/auth/logout")
                .logoutSuccessHandler(restLogoutSuccessHandler)
                .invalidateHttpSession(true)
                .deleteCookies("SESSION");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return username -> {
            List<Account> accounts = accountDao.listByAccount(username);
            if (CollUtil.isEmpty(accounts)) {
                throw new UsernameNotFoundException("");
            }

            Optional<Account> account = accounts.stream()
                    .filter(item -> !item.getLocked())
                    .findFirst();

            if (account.isPresent()) {
                return new CustomUserDetails(account.get());
            } else {
                throw new DisabledException("");
            }
        };
    }

    @Bean
    public SessionRegistry getSessionRegistry() {
        return new SessionRegistryImpl();
    }

}
